package task.snake.model;

import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

/**
 * Class SnakeModel is a model in MVC pattern.
 * 
 * @author Nadezhda Makarkina
 * 
 */
public class SnakeModel extends Observable {
	private final int m; // width of field
	private final int n; // height of field
	private final int quantity; // quantity of frogs
	private final long sleepTime; // sleep time of frog in milliseconds
	private final Snake snake;
	private final List<Frog> frogs;
	private final Cell[][] playingField;
	private int counter = 0;
	private Thread snakeThread;
	private List<Thread> frogThreads = new ArrayList<Thread>();

	/**
	 * Initializes a newly created SnakeModel object.
	 * 
	 * @param m
	 * @param n
	 * @param length
	 * @param quantity
	 * @param sleepTime
	 */
	public SnakeModel(int m, int n, int length, int quantity, long sleepTime) {
		this.m = m;
		this.n = n;
		if (length < 2) {
			length = 2;
		} else if (length >= n) {
			length = n - 2;
		}
		this.quantity = quantity;
		this.sleepTime = sleepTime;
		this.playingField = createPlayingField(m, n);
		this.snake = new Snake(new Point(0, length - 1), this, length);
		for (int i = 0; i < snake.body.size(); i++) {
			setCell(snake.body.get(i), snake, true);
		}
		this.frogs = new ArrayList<Frog>(quantity);
		for (int i = 0; i < quantity; i++) {
			frogs.add(i, createFrog());
			setCell(frogs.get(i).getHeadLocation(), frogs.get(i), true);
		}
		snakeThread = new Thread(snake);
		snakeThread.setName("Snake");
		for (int i = 0; i < quantity; i++) {
			frogThreads.add(new Thread(frogs.get(i)));
			frogThreads.get(i).setName("Frog " + i);
		}
	}

	/*
	 * Method creates play field and sets its properties.
	 */
	private static Cell[][] createPlayingField(int m, int n) {
		final Cell[][] playingField = new Cell[m][n];
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				playingField[i][j] = new Cell();
			}
		}
	/*	for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (i == 0) {
					playingField[i][j].setLeft(true);
				} else if (i == m - 1) {
					playingField[i][j].setRight(true);
				}
				if (j == 0) {
					playingField[i][j].setUp(true);
				} else if (j == n - 1) {
					playingField[i][j].setDown(true);
				}
			}
		}*/
		return playingField;
	}

	/**
	 * Method creates frog and allocate it on the playing field.
	 * 
	 * @return Frog object
	 */
	public Frog createFrog() {
		Frog f = null;
		boolean continueLoop = true;
		do {
			int x = (int) (Math.random() * m);
			int y = (int) (Math.random() * n);
			if (playingField[x][y].getCreature() == null) {
				int rand = (int) (Math.random() * 4);
				switch (rand) {
				case 0:
				case 3:
					f = new Frog(new Point(x, y), this);
					break;
				case 1:
					f = new RedFrog(new Point(x, y), this);
					break;
				case 2:
					f = new BlueFrog(new Point(x, y), this);
					break;	
				}
				continueLoop = false;
			}
		} while (continueLoop);
		return f;
	}

	/**
	 * Returns cell's properties.
	 * 
	 * @param p coordinates of cell
	 * @return cell
	 */
	public Cell getCell(Point p) {
		return playingField[p.x][p.y];
	}

	/**
	 * Sets cell's properties.
	 * 
	 * @param p position of cell on the play field
	 * @param c creature
	 * @param b boolean parameter, which indicates state of cell: free or busy
	 */
	public synchronized void setCell(Point p, Creature c, boolean b) {
		playingField[p.x][p.y].setCreature(c);
		// setup properties of fields which is next to field with p coordinates
		if (p.y > 0) {
			playingField[p.x][p.y - 1].setDown(b);
		}else{
			playingField[p.x][n - 1].setDown(b);
		}
		if (p.y < n - 1) {
			playingField[p.x][p.y + 1].setUp(b);
		}else{
			playingField[p.x][0].setUp(b);
		}
		if (p.x > 0) {
			playingField[p.x - 1][p.y].setRight(b);
		}else{
			playingField[m - 1][p.y].setRight(b);
		}
		if (p.x < m - 1) {
			playingField[p.x + 1][p.y].setLeft(b);
		}else{
			playingField[0][p.y].setLeft(b);
		}
		setChanged();
		notifyObservers();
	}

	/**
	 * Returns the snake object.
	 * 
	 * @return snake
	 */
	public Snake getSnake() {
		return snake;
	}

	/**
	 * Returns list with frog objects.
	 * 
	 * @return
	 */
	public List<Frog> getFrogs() {
		return frogs;
	}

	/**
	 * Returns counter.
	 * 
	 * @return
	 */
	public int getCounter() {
		return counter;
	}

	/**
	 * Sets counter.
	 * 
	 * @param counter
	 */
	public void setCounter(int counter) {
		this.counter = counter;
	}

	/**
	 * Returns sleep time (pause between movements).
	 * 
	 * @return
	 */
	public long getSleepTime() {
		return sleepTime;
	}

	/**
	 * Returns width of play field.
	 * 
	 * @return
	 */
	public int getWidth() {
		return m;
	}

	/**
	 * Returns height of play field.
	 * 
	 * @return
	 */
	public int getHeight() {
		return n;
	}

	/**
	 * Method creates threads for snake and frogs and starts them.
	 */
	public void startCreatures() {
		if (!snakeThread.isAlive()) {
			snakeThread.start();
			for (int i = 0; i < quantity; i++) {
				frogThreads.get(i).start();
			}
		} else {
			getSnake().setSleep(false);
			for (int i = 0; i < frogThreads.size(); i++) {
				getFrogs().get(i).setSleep(false);
			}
		}
	}

	/**
	 * Sets the state of stopped field.
	 */
	public synchronized void stopCreatures() {
		getSnake().setStopped();
		for (int j = 0; j < getFrogs().size(); j++) {
			getFrogs().get(j).setStopped();
		}
	}

	public synchronized void pauseCreatures() {
		getSnake().setSleep(true);
		for (int i = 0; i < frogThreads.size(); i++) {
			getFrogs().get(i).setSleep(true);
		}
	}

}
